Transient Fickian Diffusion

The package OpenPNM allows for the simulation of many transport phenomena in porous media such as Stokes flow, Fickian diffusion, advection-diffusion, transport of charged species, etc. Transient and steady-state simulations are both supported. An example of a transient Fickian diffusion simulation through a Cubic pore network is shown here.

First, OpenPNM is imported.


In [1]:
import numpy as np
import openpnm as op
np.random.seed(10)
%matplotlib inline
np.set_printoptions(precision=5)

Define new workspace and project


In [2]:
ws = op.Workspace()
ws.settings["loglevel"] = 40
proj = ws.new_project()

Generate a pore network

An arbitrary Cubic 3D pore network is generated consisting of a layer of $29\times13$ pores with a constant pore to pore centers spacing of ${10}^{-5}{m}$.


In [3]:
net = op.network.Cubic(shape=[29, 13, 1], spacing=1e-5, project=proj)

Create a geometry

Here, a geometry, corresponding to the created network, is created. The geometry contains information about the size of pores and throats in the network such as length and diameter, etc. OpenPNM has many prebuilt geometries that represent the microstructure of different materials such as Toray090 carbon papers, sand stone, electrospun fibers, etc. In this example, a simple geometry known as StickAndBall that assigns random diameter values to pores throats, with certain constraints, is used.


In [4]:
geo = op.geometry.StickAndBall(network=net, pores=net.Ps, throats=net.Ts)

Add a phase

Then, a phase (water in this example) is added to the simulation and assigned to the network. The phase contains the physical properties of the fluid considered in the simulation such as the viscosity, etc. Many predefined phases as available on OpenPNM.


In [5]:
phase = op.phases.Water(network=net)

Add a physics

Next, a physics object is defined. The physics object stores information about the different physical models used in the simulation and is assigned to specific network, geometry and phase objects. This ensures that the different physical models will only have access to information about the network, geometry and phase objects to which they are assigned. In fact, models (such as Stokes flow or Fickian diffusion) require information about the network (such as the connectivity between pores), the geometry (such as the pores and throats diameters), and the phase (such as the diffusivity coefficient).


In [6]:
phys = op.physics.GenericPhysics(network=net, phase=phase, geometry=geo)

The diffusivity coefficient of the considered chemical species in water is also defined.


In [7]:
phase['pore.diffusivity'] = 2e-09

Defining a new model

The physical model, consisting of Fickian diffusion, is defined and attached to the physics object previously defined.


In [8]:
mod = op.models.physics.diffusive_conductance.ordinary_diffusion
phys.add_model(propname='throat.diffusive_conductance', model=mod, regen_mode='normal')

Define a transient Fickian diffusion algorithm

Here, an algorithm for the simulation of transient Fickian diffusion is defined. It is assigned to the network and phase of interest to be able to retrieve all the information needed to build systems of linear equations.


In [9]:
fd = op.algorithms.TransientFickianDiffusion(network=net, phase=phase)

Add boundary conditions

Next, Dirichlet boundary conditions are added over the front and back boundaries of the network.


In [10]:
fd.set_value_BC(pores=net.pores('front'), values=0.5)
fd.set_value_BC(pores=net.pores('back'), values=0.2)

Define initial conditions

Initial conditions (optional) can also be specified. If they are not defined, a zero concentration is assumed at the beginning of the transient simulation.


In [11]:
fd.set_IC(0.2)

Note that both set_value_BC and set_IC also accept as input, in addition to a single scalar value, an ndarray.

Setup the transient algorithm settings

The settings of the transient algorithm are updated here. This step is optional as default settings are predefined. It is, however, important to update these settings on each new simulation as the time-scale of different phenomena in different problems may strongly differ. Here, the time discretization scheme is set to cranknicolson, which is second-order accurate in time. The two other options supported in OpenPNM are the implicit scheme (only first order accurate but faster than the cranknicolson) and the steady which simply corresponds to a steady-state simulation. Other parameters are also set; the final time step t_final, the output time stepping t_output, the computational time step t_step, and the tolerance to be achieved before reaching steady-state t_tolerance.


In [12]:
fd.setup(t_scheme='cranknicolson', t_final=100, t_output=5, t_step=1, t_tolerance=1e-12)

Note that the output time stepping t_output may a scalar, ND-array, or list. For a scalar, it is considered as an output interval. If t_output > t_final, no transient data is stored. If t_output is not a multiple of t_step, t_output will be approximated. When t_output is a list or ND-array, transient solutions corresponding to this list or array will be stored. Finally, initial, final and steady-state (if reached) solutions are always stored.

One can print the algorithm's settings as shown here.


In [13]:
print(fd.settings)


――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
key                                 value
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
cache_A                             True
cache_b                             True
conductance                         throat.diffusive_conductance
phase                               phase_01
quantity                            pore.concentration
solver_atol                         None
solver_family                       scipy
solver_maxiter                      5000
solver_preconditioner               jacobi
solver_rtol                         None
solver_tol                          1e-08
solver_type                         spsolve
prefix                              alg
max_iter                            5000
relaxation_quantity                 1.0
relaxation_source                   1.0
rxn_tolerance                       1e-05
sources                             []
variable_props                      []
t_final                             100
t_initial                           0
t_output                            5
t_precision                         12
t_scheme                            cranknicolson
t_step                              1
t_tolerance                         1e-12
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

Note that the quantity corresponds to the quantity solved for.

Run the algorithm

The algorithm is run here.


In [14]:
fd.run()

Post process and export the results

Once the simulation is successfully performed. The solution at every time steps is stored within the algorithm object. The algorithm's stored information is printed here.


In [15]:
print(fd)


――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
openpnm.algorithms.TransientFickianDiffusion : alg_01
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#     Properties                                    Valid Values
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1     pore.bc_rate                                      0 / 377  
2     pore.bc_value                                    26 / 377  
3     pore.concentration                              377 / 377  
4     pore.concentration@0                            377 / 377  
5     pore.concentration@10                           377 / 377  
6     pore.concentration@100                          377 / 377  
7     pore.concentration@15                           377 / 377  
8     pore.concentration@20                           377 / 377  
9     pore.concentration@25                           377 / 377  
10    pore.concentration@30                           377 / 377  
11    pore.concentration@35                           377 / 377  
12    pore.concentration@40                           377 / 377  
13    pore.concentration@45                           377 / 377  
14    pore.concentration@5                            377 / 377  
15    pore.concentration@50                           377 / 377  
16    pore.concentration@55                           377 / 377  
17    pore.concentration@60                           377 / 377  
18    pore.concentration@65                           377 / 377  
19    pore.concentration@70                           377 / 377  
20    pore.concentration@75                           377 / 377  
21    pore.concentration@80                           377 / 377  
22    pore.concentration@85                           377 / 377  
23    pore.concentration@90                           377 / 377  
24    pore.concentration@95                           377 / 377  
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
#     Labels                                        Assigned Locations
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
1     pore.all                                      377       
2     throat.all                                    712       
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――

Note that the solutions at every exported time step contain the @ character followed by the time value. Here the solution is exported after each $5s$ in addition to the final time step which is not a multiple of $5$ in this example.

To print the solution at $t=10s$


In [16]:
fd['pore.concentration@10']


Out[16]:
array([0.5    , 0.5    , 0.5    , 0.5    , 0.5    , 0.5    , 0.5    ,
       0.5    , 0.5    , 0.5    , 0.5    , 0.5    , 0.5    , 0.48985,
       0.48723, 0.49038, 0.49194, 0.48699, 0.48315, 0.48278, 0.4898 ,
       0.48944, 0.48915, 0.49516, 0.49076, 0.48953, 0.47753, 0.47827,
       0.47857, 0.47901, 0.4718 , 0.47375, 0.47268, 0.47452, 0.47135,
       0.47279, 0.47555, 0.47392, 0.47405, 0.46762, 0.46962, 0.46894,
       0.465  , 0.46189, 0.46334, 0.46329, 0.46056, 0.45397, 0.45584,
       0.45682, 0.45765, 0.45733, 0.45788, 0.45705, 0.45518, 0.4525 ,
       0.45286, 0.45492, 0.45505, 0.45078, 0.44657, 0.44535, 0.44398,
       0.44088, 0.44049, 0.44085, 0.43928, 0.43807, 0.44006, 0.44205,
       0.44025, 0.44213, 0.44068, 0.43802, 0.43599, 0.4312 , 0.4296 ,
       0.42725, 0.42481, 0.42289, 0.42033, 0.42449, 0.42658, 0.42035,
       0.42271, 0.42122, 0.42028, 0.41902, 0.416  , 0.42104, 0.41975,
       0.40835, 0.40689, 0.40896, 0.4127 , 0.41117, 0.40692, 0.40593,
       0.40127, 0.40232, 0.40554, 0.40779, 0.40419, 0.40967, 0.39175,
       0.38916, 0.3958 , 0.39526, 0.39444, 0.3982 , 0.39234, 0.38942,
       0.39026, 0.38889, 0.38518, 0.37572, 0.38489, 0.37872, 0.3795 ,
       0.38142, 0.37739, 0.37594, 0.37913, 0.37777, 0.37873, 0.38138,
       0.36934, 0.36239, 0.35907, 0.36143, 0.36172, 0.36533, 0.36563,
       0.36691, 0.36819, 0.36702, 0.36948, 0.36666, 0.36102, 0.35022,
       0.34915, 0.35033, 0.34416, 0.34835, 0.35071, 0.35125, 0.35232,
       0.35783, 0.36136, 0.3574 , 0.35179, 0.34562, 0.34337, 0.34289,
       0.3402 , 0.335  , 0.34055, 0.33973, 0.33702, 0.3339 , 0.33747,
       0.33994, 0.33978, 0.3374 , 0.33583, 0.33492, 0.33427, 0.33064,
       0.32875, 0.33268, 0.33027, 0.32396, 0.32307, 0.32244, 0.32031,
       0.32373, 0.32649, 0.3266 , 0.32515, 0.32574, 0.32322, 0.32283,
       0.31907, 0.31754, 0.31629, 0.31602, 0.31535, 0.31081, 0.30895,
       0.31337, 0.31352, 0.31517, 0.31399, 0.3128 , 0.31338, 0.30465,
       0.30433, 0.30845, 0.30423, 0.30125, 0.2991 , 0.29621, 0.3003 ,
       0.30224, 0.30482, 0.30096, 0.30284, 0.30389, 0.29616, 0.29793,
       0.2968 , 0.29037, 0.28961, 0.2917 , 0.28974, 0.28954, 0.28762,
       0.28684, 0.28443, 0.2854 , 0.28694, 0.29074, 0.29137, 0.2868 ,
       0.28482, 0.28264, 0.28109, 0.28373, 0.27789, 0.27111, 0.27047,
       0.26967, 0.26771, 0.26938, 0.28599, 0.27943, 0.27519, 0.27419,
       0.27218, 0.2688 , 0.2711 , 0.26745, 0.26707, 0.26529, 0.26348,
       0.26283, 0.26309, 0.27055, 0.26777, 0.26597, 0.26387, 0.26443,
       0.26502, 0.26318, 0.2595 , 0.25917, 0.25741, 0.25594, 0.25818,
       0.25809, 0.25714, 0.25873, 0.25719, 0.25741, 0.25733, 0.25598,
       0.25579, 0.25433, 0.25266, 0.25088, 0.25126, 0.25378, 0.25481,
       0.24648, 0.24918, 0.25061, 0.2531 , 0.25048, 0.24513, 0.24727,
       0.24878, 0.24757, 0.24713, 0.24788, 0.24863, 0.25074, 0.23656,
       0.23877, 0.24173, 0.24041, 0.23972, 0.2394 , 0.24037, 0.24333,
       0.24205, 0.24301, 0.2429 , 0.24272, 0.24167, 0.23029, 0.23023,
       0.22956, 0.22732, 0.22956, 0.23458, 0.234  , 0.23548, 0.23505,
       0.23738, 0.2375 , 0.23337, 0.23254, 0.22386, 0.22299, 0.22148,
       0.22358, 0.22389, 0.22518, 0.22616, 0.22578, 0.22575, 0.22668,
       0.22498, 0.22102, 0.22291, 0.21738, 0.21611, 0.21797, 0.21817,
       0.21756, 0.21559, 0.21691, 0.21646, 0.21993, 0.21837, 0.21565,
       0.21497, 0.21368, 0.21166, 0.21235, 0.21204, 0.21277, 0.21297,
       0.21245, 0.20979, 0.20945, 0.21171, 0.21232, 0.21184, 0.2094 ,
       0.20768, 0.2089 , 0.20687, 0.20493, 0.20622, 0.20634, 0.20611,
       0.20535, 0.20494, 0.20495, 0.20546, 0.20566, 0.20486, 0.20396,
       0.2    , 0.2    , 0.2    , 0.2    , 0.2    , 0.2    , 0.2    ,
       0.2    , 0.2    , 0.2    , 0.2    , 0.2    , 0.2    ])

The solution is here stored in the phase before export.


In [17]:
phase.update(fd.results())

Export the results into an xdmf file to be able to play an animation of the time dependent concentration on Paraview.


In [18]:
proj.export_data(phases=[phase], filename='./results/out', filetype='xdmf')

Visialization using Matplotlib

One can perform post processing and visualization using the exported files on an external software such as Paraview. Additionally, the Pyhton library Matplotlib can be used as shown here to plot the concentration color map at steady-state.


In [19]:
#NBVAL_IGNORE_OUTPUT
import matplotlib.pyplot as plt
c = fd['pore.concentration'].reshape((net._shape))
fig, ax = plt.subplots(figsize=(6, 6))
plt.imshow(c[:,:,0])
plt.title('Concentration (mol/m$^3$)')
plt.colorbar();